package projects.ufam.nodes.nodeImplementations;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

import projects.defaultProject.nodes.nodeImplementations.LocalizableNode;
import projects.defaultProject.nodes.timers.MessageTimer;
import projects.defaultProject.nodes.timers.WaitTimer;
import projects.defaultProject.nodes.timers.WakeupTimer;
import projects.ufam.CustomGlobal;
import projects.ufam.nodes.messages.A3ChildRecognitionMessage;
import projects.ufam.nodes.messages.A3HelloMessage;
import projects.ufam.nodes.messages.A3ParentRecognitionMessage;
import projects.ufam.nodes.messages.A3SleepingMessage;
import sinalgo.nodes.Node;
import sinalgo.nodes.NodeState;
import sinalgo.nodes.messages.Message;
import sinalgo.runtime.Global;
import sinalgo.tools.statistics.Distribution;

public class A3Node extends LocalizableNode implements Comparable {
	Random random = new Random(Distribution.getSeed());
	protected enum A3State {
		WaitingChildren, WaitingSiblings, LastChance, ShouldSleep
	};

	WaitTimer wt = null;
	protected int redundance = 1;
	protected int received = 0;
	protected A3State a3State = null;
	int rank = -1;
	protected boolean covered = false;
	protected A3Node parent = null;
	protected ArrayList<A3Node> candidates = new ArrayList<A3Node>();

	public void startDensityControl() {
		this.switchState(NodeState.WakingUp);
		if (this.isSink()) {
			this.switchState(NodeState.Enabled);
			A3HelloMessage msg = new A3HelloMessage();
			MessageTimer t = new MessageTimer(msg);
			if (Global.currentTime == 1.0 && isSink()) {
				t.startRelative(250, this);
				wt = new WaitTimer(null);
				wt.startRelative(260, this);
			} else {
				t.startRelative(1, this);
				wt = new WaitTimer(null);
				wt.startRelative(10, this);
			}
			a3State = A3State.WaitingChildren;
			candidates.clear();
			covered = true;
		}
	}

	public void preStep() {
		super.preStep();

		if (Global.currentTime % CustomGlobal.densityControlRoundLength == 0) {
			cache.clear();
			candidates.clear();
			a3State = null;
			parent = null;
			covered = false;
			rank = -1;
			resetRouting();
			if (wt != null)
				wt.setEnabled(false);
			startDensityControl();
		}
	}

	protected void handleHello(Message msg, Node sender) {
		if (!covered) {
			this.parent = (A3Node) sender;
			covered = true;
			A3ParentRecognitionMessage m = new A3ParentRecognitionMessage();
			this.send(m, sender);
		}
	}

	public boolean shouldHandle(Message msg, Node sender) {
		return true;
	}

	public void handleMessage(Message msg, Node sender) {
		LocalizableNode ln = (LocalizableNode) sender;
		if (!shouldHandle(msg, sender)) {
			super.handleMessage(msg, sender);
			return;
		}
		if (msg instanceof A3HelloMessage) {
			handleHello(msg, sender);
		} else if (msg instanceof A3ParentRecognitionMessage) {
			a3State = A3State.WaitingChildren;
			candidates.add((A3Node) sender);
		} else if (msg instanceof A3ChildRecognitionMessage) {
			if (parent != null && a3State == null) {
				A3ChildRecognitionMessage m = (A3ChildRecognitionMessage) msg;
				rank = m.getCandidates().indexOf(this);
				rank /= redundance;
				int i = rank * 10;
				i++;
				i += random.nextInt(10);
				if (i > 100) {
					i = 100 + random.nextInt(50);
				}
				a3State = A3State.WaitingSiblings;
				if (wt != null)
					wt.setEnabled(false);
				wt = new WaitTimer(null);
				wt.startRelative(i, this);
			}
		} else if (msg instanceof A3SleepingMessage) {
			if (this.parent == ((A3SleepingMessage) msg).getParent()
					&& ((A3Node) sender).rank < this.rank) {
				a3State = A3State.LastChance;
				covered = true;
				WakeupTimer wake = new WakeupTimer();
				wake.setEnabled(true);
				switchState(NodeState.StandBy);
				wake.startRelative(200 + random
						.nextInt(100), this);
			}
		} else
			super.handleMessage(msg, sender);
	}

	public void afterWait(Message m) {
		if (a3State == A3State.WaitingChildren) {
			if (candidates.size() == 0) {
				switchState(NodeState.StandBy);
				a3State = null;
				return;
			}
			Collections.sort(candidates);
			A3ChildRecognitionMessage msg = new A3ChildRecognitionMessage(
					candidates);
			for (A3Node n : candidates) {
				this.send(msg, n);
			}
			if (wt != null)
				wt.setEnabled(false);
		} else if (a3State == A3State.WaitingSiblings) {
			A3SleepingMessage msg = new A3SleepingMessage(this.parent);
			this.switchState(NodeState.Enabled);
			this.broadcast(msg);
			A3HelloMessage msg2 = new A3HelloMessage();
			MessageTimer t = new MessageTimer(msg2);
			t.startRelative(5, this);
			if (wt != null)
				wt.setEnabled(false);
			wt = new WaitTimer(null);
			wt.startRelative(10, this);
			a3State = A3State.WaitingChildren;
		} else if (a3State == A3State.ShouldSleep) {
			this.switchState(NodeState.StandBy);
		}
	}

	public void wakeUp() {
		if (a3State == A3State.LastChance) {
			A3HelloMessage msg = new A3HelloMessage();
			this.broadcast(msg);
			switchState(NodeState.WakingUp);
			a3State = A3State.ShouldSleep;
			candidates.clear();
			if (wt != null)
				wt.setEnabled(false);
			wt = new WaitTimer(null);
			wt.startRelative(10, this);
		}
	}

	@Override
	public int compareTo(Object o) {
		LocalizableNode o2 = (LocalizableNode) o;
		int c = o2.ID > this.ID ? -1 : 1;
		if (o == null || this.parent == null)
			return c;
		
		double d1 = this.parent.estimateDistance(o2);
		double d2 = this.parent.estimateDistance(this);
		double we = 5.0;
		double wd = 0.5;
		d1 = d1 * wd + we * ((LocalizableNode) o).getEnergyLevel();
		d2 = d2 * wd + we * this.getEnergyLevel();

		if (CustomGlobal.useResidualOnDC) {
			double r1 = this.getResidual() < 0.001 ? 0.001 : (this
					.getResidual() > 500 ? 10 : this.getResidual());

			double r2 = o2.getResidual() < 0.001 ? 0.001
					: (o2.getResidual() > 500 ? 10 : o2.getResidual());

			d1 = r1;
			d2 = r2;
		}

		return (d1 < d2 ? -1 : (d1 == d2 ? c : 1));
	}
}
